/*
 * MPEGFeature.cpp
 *        -- feature of MPEG file
 *
 * Author: Qiu Shuang
 * Date: April 11th, 2011
 */
#include <list>

#include "MPEGFile.h"
#include "MPEGFrameData.h"
#include "MPEGGranule.h"

#include "MPEGFeature.h"

#define BITS_PER_BYTE   8

/* constructor */
/* take an original data source as input and align different window */
MPEGFeature::MPEGFeature(MPEGFile *dataSource):_feature_length(0), _granules(0),
    _blocks(0), _feature(NULL), _data(NULL), _content(dataSource){
        if (_content){
            const std::list<MPEGFrameData *> &frames = _content->contents();
#ifdef DEBUG
    printf("number of frame %d\n", frames.size());
#endif
            _granules = frames.size() * GRANULE;
            _data = (pGranuleData)calloc(_granules, sizeof(GranuleData));
            _granules = 0;                              /* used as index into processed data */

            std::list<MPEGFrameData *>::const_iterator itr = frames.begin();
            std::list<MPEGFrameData *>::const_iterator itr_end = frames.end();

            while(itr != itr_end){
                preprocessFrame(*itr);

                itr++;
            }
        }else{
            printf("No MPEG data specified can't intialize feature.\n");
        }
#ifdef DEBUG
    if (_granules != _content->contents().size() * 2){
        printf("error decoding, some granule must be missing. granules read %d\n", _granules);
        getc(stdin);

        exit(-1);
    }else{
        printf("extract done.\n");
    }
#endif
}

MPEGFeature::~MPEGFeature(){
    delete [] _data;
}

/* rearrange granules into block, block size is specified */
void MPEGFeature::block(int size){
    /* number_of_block = number_of_granule - block_size + 1 */
    _blocks = _granules - size + 1;
    _features = (pGranuleFeature)calloc(_blocks, sizeof(GranuleFeature));

    int block, granule_offset, channel, subband, index, end;
    double cursor = 0.0;
    for(block = 0; block < _blocks; block++){
        for(subband = 0; subband < DIVIDED_BANDS; subband++){
            /* determine subband delimeter */
            index = DIVIDED_SUBBAND[subband];
            end = DIVIDED_SUBBAND[subband + 1];

            for(granule_offset = 0; granule_offset < size; granule_offset++){
                for(channel = 0; channel < CHANNEL; channel++){
                    for(; index < end; index++){
                        cursor = _data[block + granule_offset].channel[channel][index];
                        _features[block].subband[subband][channel] += cursor;
                        _features[block].subband[DIVIDED_BANDS][channel] += cursor;
                    }
                }
            }
        }
    }
}

void MPEGFeature::extractFeature(){
    int block, subband, channel;

    for(block = 0; block < _blocks; block++){
        for(subband = 0; subband < DIVIDED_BANDS; subband++){
            for(channel = 0; channel < CHANNEL; channel++){
                _features[block].subband[subband][channel] /= _features[block].subband[DIVIDED_BANDS][channel];
            }
        }

        /* reset for entropy computation */
        _features[block].subband[DIVIDED_BANDS][0] = 0.0;
        _features[block].subband[DIVIDED_BANDS][1] = 0.0;
    }

    double probability = 0.0;
    /* evaluate entropy */
    for(block = 0; block < _blocks; block++){
        for(subband = 0; subband < DIVIDED_BANDS; subband++){
            for(channel = 0; channel < CHANNEL; channel++){
                probability = _features[block].subband[subband][channel];
                /* sum entropy up */
                _features[block].subband[DIVIDED_BANDS][channel] += -probability * log(probability);
            }
        }
    }

#ifdef DEBUG
    printf("total bits of feature: %d\n", _blocks -1);
#endif

    _feature_length = (_blocks - 1) * CHANNEL;          /* for stereo case */
    int feature_bytes = _feature_length / BITS_PER_BYTE + !!(_feature_length % BITS_PER_BYTE);

    _feature = (unsigned char *)calloc(feature_bytes, sizeof(unsigned char));

    /* summarize two channel [l][r][l][r][...][l][r] */
    int offset = 0, index = 0;
    for(block = 1; block < _blocks; block++){
        for(channel = 0; channel < CHANNEL; channel++){
            if(_features[block].subband[DIVIDED_BANDS][channel] > _features[block - 1].subband[DIVIDED_BANDS][channel]){
                _feature[index] |= 1 << (BITS_PER_BYTE - 1 - offset);
                index += offset / BITS_PER_BYTE;
                offset %= BITS_PER_BYTE;
            }
        }
    }
}

void MPEGFeature::preprocessFrame(MPEGFrameData *frame){
    int granule, channel;
    int src_idx = 0, des_idx = 0;
    for (granule = 0; granule < GRANULE; granule++, _granules++){
        MPEGGranule *data = frame->getGranule(granule);

        for (channel = 0; channel < CHANNEL; channel++){
            double *original = (double *)data->getMDCTCoefficients(channel);
            while(des_idx < ALIGNED_SAMPLES){
                _data[_granules].channel[channel][des_idx] += original[src_idx++];
                _data[_granules].channel[channel][des_idx] += original[src_idx++];
                _data[_granules].channel[channel][des_idx] += original[src_idx++];

                _data[_granules].channel[channel][des_idx++] /= 3;
            }
        }
    }
}

/* fuzzy match a short bit pattern in a long one, if bit error rate is under threshold */
/*******************************************************************/
/*         cursor also need to be returned                         */
/*******************************************************************/
int MPEGFeature::search(MPEGFeature *shortPattern, double threshold){
    unsigned char *shift_0 = NULL;       /* original feature */
    unsigned char *shift_2 = NULL;       /* feature with 2 bits shifted */
    unsigned char *shift_4 = NULL;       /* feature with 4 bits shifted */
    unsigned char *shift_6 = NULL;       /* feature with 6 bits shifted */

    int bits = shortPattern->length();
    int short_bytes = BITS_TO_BYTES(bits);
    int long_bytes = BITS_TO_BYTES(_feature_length);

    int mismatch = bits;                  /* number of bits mismatched */
    int cursor = -1;                      /* point to start of max match */
    int max_mismatch = bits;

    shift_0 = (unsigned char *)calloc(BITS_TO_BYTES(bits), sizeof(unsigned char));
    shift_2 = (unsigned char *)calloc(BITS_TO_BYTES(bits), sizeof(unsigned char));
    shift_4 = (unsigned char *)calloc(BITS_TO_BYTES(bits + 4), sizeof(unsigned char));
    shift_6 = (unsigned char *)calloc(BITS_TO_BYTES(bits + 6), sizeof(unsigned char));

    /* get original data */
    memcpy((void *)shift_0, (void *)shortPattern->getFeature(), BITS_TO_BYTES(bits));

    /* right shift 2 bits */
    unsigned char offset = 0;
    unsigned char tmp;
    int index;
    int total_bytes = BITS_TO_BYTES(bits + 2);
    for(index = 0; index < total_bytes; index++){
        tmp = offset;                       /* holding offset bits */
        offset = *(shift_2 + index);
        *(shift_2 + index) >>= 2;
        *(shift_2 + index) |= tmp << 6;
    }

    /* right shift 4 bits */
    offset = 0;
    total_bytes = BITS_TO_BYTES(bits + 4);
    for(index = 0; index < total_bytes; index++){
        tmp = offset;
        offset = *(shift_4 + index);
        *(shift_4 + index) >>= 4;
        *(shift_4 + index) |= tmp << 4;
    }

    /* right shift 6 bits */
    offset = 0;
    total_bytes = BITS_TO_BYTES(bits + 6);
    for(index = 0; index < total_bytes; index++){
        tmp = offset;
        offset = *(shift_6 + index);
        *(shift_6 + index) >>= 6;
        *(shift_6 + index) |= tmp << 2;
    }

    while(index + short_bytes < long_bytes){
        mismatch = internal_search(shift_0, index, short_bytes);

        if (mismatch < max_mismatch){
            cursor = index;
            max_mismatch = mismatch;

            //if (max_mismatch = bits){           /* return ASAP */
            //    return max_mismatch;
            //}
        }

        mismatch = internal_search(shift_2, index, short_bytes);
        if (mismatch < max_mismatch){
            cursor = index;
            max_mismatch = mismatch;

            //if (max_mismatch = bits){           /* return ASAP */
            //    return max_mismatch;
            //}
        }

        mismatch = internal_search(shift_4, index, short_bytes);
        if (mismatch < max_mismatch){
            cursor = index;
            max_mismatch = mismatch;

            //if (max_mismatch = bits){           /* return ASAP */
            //    return max_mismatch;
            //}
        }

        mismatch = internal_search(shift_6, index, short_bytes);
        if (mismatch < max_mismatch){
            cursor = index;
            max_mismatch = mismatch;

            //if (max_mismatch = bits){           /* return ASAP */
            //    return max_mismatch;
            //}
        }

        index++;
    }

    return max_mismatch;
}

/* find in specified data array */
int MPEGFeature::internal_search(unsigned char *data, int start, int length){
    int total_mismatch = 0;
    int mismatch = 0;

    int itr = 0;
    while((itr < length) && (start + itr < length)){
        mismatch = HAMMING_WEIGHT(_feature[start + itr] ^ data[itr], 1, MASK_1);
        mismatch = HAMMING_WEIGHT(mismatch, 2, MASK_2);
        mismatch = HAMMING_WEIGHT(mismatch, 4, MASK_4);

        total_mismatch += mismatch;

        itr++;
    }

    return total_mismatch;
}